package cn.bytets.rpc.common.bytecode;

import cn.bytets.rpc.common.utils.ClassGeneratorUtils;
import cn.bytets.rpc.common.utils.ClassUtils;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 使用 Javassist 生成代理类
 */
public  class JavassistProxyGenerate {

    private static final AtomicLong PROXY_CLASS_COUNTER = new AtomicLong(0);

    private static final String PACKAGE_NAME = Proxy.class.getPackage().getName();

    private static final Map<ClassLoader, Map<String, Object>> PROXY_CACHE_MAP = new WeakHashMap<ClassLoader, Map<String, Object>>();

    private static final Object PENDING_GENERATION_MARKER = new Object();

    public static Proxy getProxy(Class<?> ics){
        return getProxy(ClassUtils.getClassLoader(Proxy.class),ics);
    }

    public static Proxy getProxy(ClassLoader cl,Class<?> ics){
        if (!ics.isInterface()){
            throw new RuntimeException(ics + " is not a interface.");
        }
        Class<?> tmp = null;
        try {
            tmp = Class.forName(ics.getName(), false, cl);
        } catch (ClassNotFoundException e) {
        }
        if (tmp != ics) {
            throw new IllegalArgumentException(ics + " is not visible from class loader");
        }

        // get cache by class loader.
        final Map<String, Object> cache;
        synchronized (PROXY_CACHE_MAP) {
            cache = PROXY_CACHE_MAP.computeIfAbsent(cl, k -> new HashMap<>());
        }
        long id = PROXY_CLASS_COUNTER.getAndIncrement();
        String pcn = ics.getPackage().getName()+".proxy"+id;
        Proxy proxy = null;
        synchronized (cache) {
            do {
                Object value = cache.get(pcn);
                if (value instanceof Reference<?>) {
                    proxy = (Proxy) ((Reference<?>) value).get();
                    if (proxy != null) {
                        return proxy;
                    }
                }

                if (value == PENDING_GENERATION_MARKER) {
                    try {
                        cache.wait();
                    } catch (InterruptedException e) {
                    }
                } else {
                    cache.put(pcn, PENDING_GENERATION_MARKER);
                    break;
                }
            }
            while (true);
        }

        ClassGenerator ccp = null,ccm = null;
        try {
            ccp = ClassGenerator.newInstance(cl);
            ccp.addField(Modifier.PRIVATE, InvocationHandler.class.getName(),"handler");
            /**
             * 被代理接口类的所有方法数组
             */
            ccp.addField(Modifier.STATIC, Method.class.getName()+"[]","methods");
            /**
             * 添加无参构造器
             */
            ccp.addDefaultConstructor();
            /**
             * 添加 InvocationHandler 构造器
             */
            ccp.addConstructor(Modifier.PUBLIC,new Class[]{InvocationHandler.class},"$0.handler = $1;");
            /**
             * 将被代理类添加为父类
             */
            ccp.addInterface(ics.getName());
            /**
             * 生成代理方法,方法主体由 InvocationHandler 实现类统一处理
             */
            List<Method> methods = new ArrayList<>();
            for (Method method:ics.getMethods()){
                if (Modifier.isStatic(method.getModifiers())) {
                    continue;
                }
                int ix = methods.size();
                StringBuffer code = new StringBuffer();
                Class<?>[] parameterTypes = method.getParameterTypes();
                /**
                 * 添加接口类代理的invoke处理方法
                 * Object ret = handler.invoke(Object proxy,Method method,args);
                 */
                code.append("Object[] args = new Object[").append(parameterTypes.length).append("];");
                for (int i=0;i<parameterTypes.length;i++){
                    /**
                     * 入参转型
                     */
                    code.append(" args[").append(i).append("] = ($w)$").append(i + 1).append(";");
                }
                code.append(" Object ret = handler.invoke(this,methods[").append(ix).append("], args);");
                if (!Void.TYPE.equals(method.getReturnType())) {
                    /**
                     * 根据返回类型转换
                     */
                    code.append(" return ").append(ClassGeneratorUtils.asArgument(method.getReturnType(), "ret")).append(";");
                }
                ccp.addMethod(method,code.toString());
                methods.add(method);
            }
            /**
             * 这个是处理接口类的代理
             */
            ccp.addClassName(pcn);
            Class<?> clazz = ccp.toClass();
            /**
             * 类里面的 methods 赋值
             */
            clazz.getField("methods").set(null, methods.toArray(new Method[0]));

            /**
             * 生成代理类
             */
            ccm = ClassGenerator.newInstance(cl);
            // create Proxy class.
            String fcn = Proxy.class.getName() + id;
            ccm = ClassGenerator.newInstance(cl);
            ccm.addClassName(fcn);
            ccm.addDefaultConstructor();
            ccm.addSuperclass(Proxy.class.getName());
            ccm.addMethod(Modifier.PUBLIC,"newInstance",
                    new Class[]{InvocationHandler.class},Object.class,new Class[0],"return new " + pcn + "($1); ");
            Class<?> pc = ccm.toClass();
            proxy = (Proxy) pc.newInstance();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            // release ClassGenerator
            if (ccp != null) {
                ccp.release();
                ccp = null;
            }
            if (ccm != null) {
                ccm.release();
                ccm = null;
            }
            synchronized (cache) {
                if (proxy == null) {
                    cache.remove(pcn);
                } else {
                    cache.put(pcn, new WeakReference<Proxy>(proxy));
                }
                cache.notifyAll();
            }
        }
        return proxy;
    }
}
